﻿using System;
using System.Collections.Generic;
using QTPlugin;
using QTPlugin.Interop;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using Microsoft.Win32;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.ComponentModel;
using System.Text.RegularExpressions;

namespace Kawanon.QtHoney
{
	public class PathExpander
	{
		private static Regex s_regCurrentFolder = new Regex("%c%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFolders = new Regex("%d%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelectionOrCurrentFolder = new Regex("%cd%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFiles = new Regex("%f%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelection = new Regex("%s%", RegexOptions.Compiled | RegexOptions.IgnoreCase);

		private string m_currentFolder;
		private string m_selectionOrCurrentFolder;
		private string m_strFiles = String.Empty, m_strFolders = String.Empty, m_strSelection = String.Empty;

		public PathExpander(Address currentFolder, Address[] selection)
		{
			m_currentFolder = Utility.Quote(Utility.AddressToPath(currentFolder));
			m_currentFolder = m_currentFolder != null ? m_currentFolder : String.Empty;

			GenerateStrings(selection);

			m_selectionOrCurrentFolder = (m_strFolders != String.Empty) ? m_strFolders : m_currentFolder;
		}

		private string Concat(string[] paths)
		{
			return String.Join(" ", Array.ConvertAll(paths, path => Utility.Quote(path)));
		}

		private void GenerateStrings(Address[] addrs)
		{
			if (addrs == null)
			{
				return;
			}

			string[] paths = Array.ConvertAll<Address, string>(addrs, Utility.AddressToPath);

			paths = Array.FindAll(paths, path => !String.IsNullOrEmpty(path));
			// 仮想フォルダも含める
			string[] folders = Array.FindAll(paths, path => !File.Exists(path));
			string[] files = Array.FindAll(paths, path => Array.IndexOf(folders, path) == -1);

			m_strSelection = Concat(paths);
			m_strFiles = Concat(files);
			m_strFolders = Concat(folders);
		}

		public string Replace(string str)
		{
			if (str == null)
			{
				return null;
			}
			str = s_regSelection.Replace(str, m_strSelection);
			str = s_regFiles.Replace(str, m_strFiles);
			str = s_regFolders.Replace(str, m_strFolders);
			str = s_regCurrentFolder.Replace(str, m_currentFolder);
			str = s_regSelectionOrCurrentFolder.Replace(str, m_selectionOrCurrentFolder);
			return str;
		}
	}

	public class NWebBrowser : NativeWindow
	{
		private const uint WM_SETFOCUS = 0x0007;
		private const uint WM_SHOWWINDOW = 0x0018;
		private const uint WM_PAINT = 0x000F;

		protected override void WndProc(ref Message m)
		{
			//switch ((uint)m.Msg)
			//{
			//}

			base.WndProc(ref m);
		}
	}

	public class YesNoConverter : StringConverter
	{
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			return destinationType == typeof(bool);
		}

		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string);
		}

		public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(string) && value is bool)
			{
				return (bool)value ? "はい" : "いいえ";
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
		{
			if (value is string)
			{
				string str = (string)value;
				if (str == "はい" || str == "True")
				{
					return true;
				}
				else if (str == "いいえ" || str == "False")
				{
					return false;
				}
				throw new ArgumentException("unknown format: " + str);
			}
			return base.ConvertFrom(context, culture, value);
		}

		public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
		{
			return new StandardValuesCollection(new bool[] { true, false });
		}

		public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
		{
			return true;
		}
	}

	public static class Utility
	{
		const uint GW_HWNDFIRST = 0;
		const uint GW_HWNDLAST = 1;
		const uint GW_HWNDNEXT = 2;
		const uint GW_HWNDPREV = 3;
		const uint GW_OWNER = 4;
		const uint GW_CHILD = 5;
		const uint GW_ENABLEDPOPUP = 6;

		private const uint S_OK = 0;

		const string CFSTR_SHELLIDLIST = "Shell IDList Array";

		const uint CF_HDROP = 15;

		[DllImport("user32.dll", SetLastError = true)]
		static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

		[DllImport("user32.dll", CharSet = CharSet.Auto)]
		static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		static extern uint RegisterClipboardFormat(string lpszFormat);

		[DllImport("kernel32.dll")]
		static extern IntPtr GlobalLock(IntPtr hMem);

		[DllImport("kernel32.dll")]
		static extern bool GlobalUnlock(IntPtr hMem);

		[DllImport("ole32.dll")]
		static extern void ReleaseStgMedium(ref STGMEDIUM pmedium);

		[DllImport("shell32.dll")]
		static extern IntPtr ILClone(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern IntPtr ILCombine(IntPtr pidl1_ABSOLUTE, IntPtr pidl2_RELATIVE);

		[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
		static extern IntPtr ILCreateFromPath(string pszPath);

		[DllImport("shell32.dll")]
		static extern IntPtr ILFindLastID(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern uint ILGetSize(IntPtr pidl);

		[DllImport("shell32.dll")]
		static extern void ILFree(IntPtr pidl_RELATIVE);

		[DllImport("shell32.dll")]
		static extern uint DragQueryFile(IntPtr hDrop, uint iFile, [Out] StringBuilder lpszFile, uint cch);

		[DllImport("shell32.dll")]
		static extern uint DragQueryFile(IntPtr hDrop, uint iFile, IntPtr zero, uint cch);

		[DllImport("ole32.dll")]
		static extern IntPtr CoTaskMemAlloc(uint cb);

		[DllImport("ole32.dll")]
		static extern void CoTaskMemFree(IntPtr pv);

		[DllImport("shell32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);

		//[StructLayout(LayoutKind.Sequential)]
		//struct CIDA
		//{
		//    /// <summary>
		//    ///  Number of PIDLs that are being transferred, not counting the parent folder.
		//    /// </summary>
		//    public uint cidl;

		//    /// <summary>
		//    /// An array of offsets, relative to the beginning of this structure. The array contains
		//    /// cidl+1 elements. The first element of aoffset contains an offset to the fully-qualified
		//    /// PIDL of a parent foloder. If this PIDL is empty, the parent folder is the desktop.
		//    /// Each of the remaining elements of the array contains an offset to one of the PIDLs to be
		//    /// transferred. ALL of these PIDLs are relative to the PIDL of the parent folder.
		//    /// </summary>
		//    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
		//    public uint[] aoffset;
		//}

		public static object ReflectGetFieldValue(object obj, string name)
		{
			MemberInfo[] members = (MemberInfo[])obj.GetType().GetMember(name,
				MemberTypes.Field, BindingFlags.Instance | BindingFlags.NonPublic);
			if (members == null || members.Length == 0)
			{
				return null;
			}
			FieldInfo field = (FieldInfo)members[0];
			return field.GetValue(obj);
		}

		//public static Stream GetResourceStream(string name)
		//{
		//    Assembly asm = Assembly.GetExecutingAssembly();
		//    string str = String.Format("{0}.res.{1}", typeof(Utility).Namespace, name);
		//    return asm.GetManifestResourceStream(str);
		//}

		public static void ShowForm(IPluginServer server, Form form)	// from GroupEditor
		{
			RECT rc = new RECT();
			if (GetWindowRect(server.ExplorerHandle, out rc))
			{
				form.StartPosition = FormStartPosition.Manual;
				form.Left = rc.left + (rc.Width - form.Width) / 2;
				form.Top = rc.top + (rc.Height - form.Height) / 2;
			}
			else
			{
				form.StartPosition = FormStartPosition.CenterScreen;
			}

			// ツールバーウィンドウを親に（ExplorerHandle は null が返るので）
			Control parent = Control.FromHandle(server.Handle);
			form.Show(parent);

			//MessageBox.Show(string.Format("{0} {1} {2} {3}", rc.right, rc.top, parent.Width, parent.Height ));
		}

		public static Control GetBandObject(IPluginServer server)
		{
			// QTTabBarClass を取得
			return Control.FromHandle(server.Handle);
		}

		public static IShellBrowser GetShellBrowser(IPluginServer server)
		{
			return (IShellBrowser)ReflectGetFieldValue(server, "shellBrowser");
		}

		public static bool IsDebugMode()
		{
			RegistryKey key = PluginInformation.OpenRegistryKey(false);
			if (key == null)
			{
				return false;
			}
			int val = (int)key.GetValue("Debug", 0);
			return val != 0;
		}

		public delegate T ModalInvoker<T>();

		public static T DoModal<T>(IPluginServer server, ModalInvoker<T> func)
		{
			if (server != null)
			{
				server.ExecuteCommand(Commands.SetModalState, true);
			}
			try
			{
				return func();
			}
			finally
			{
				if (server != null)
				{
					server.ExecuteCommand(Commands.SetModalState, false);
				}
			}
		}

		public static DialogResult DoModal(IPluginServer server, Form form)
		{
			if (server != null)
			{
				server.ExecuteCommand(Commands.SetModalState, true);
			}
			try
			{
				return form.ShowDialog();
			}
			finally
			{
				if (server != null)
				{
					server.ExecuteCommand(Commands.SetModalState, false);
				}
			}
		}

		public static Predicate<IntPtr> EqualsClassName(string name)
		{
			return delegate(IntPtr hwnd)
			{
				StringBuilder buf = new StringBuilder(32);
				if (GetClassName(hwnd, buf, 32) == 0)
				{
					return false;
				}
				return buf.ToString() == name;
			};
		}

		public static IntPtr FindChildWindow(IntPtr hwnd, Predicate<IntPtr> pred)
		{
			IntPtr child = GetWindow(hwnd, GW_CHILD);
			while (child != IntPtr.Zero)
			{
				if (pred(child))
				{
					return child;
				}
				// 孫以下を検索
				//IntPtr ret = FindChildWindow(child, pred);
				//if (ret != IntPtr.Zero)
				//{
				//    return ret;
				//}

				child = GetWindow(child, GW_HWNDNEXT);
			}
			return IntPtr.Zero;
		}

		private delegate T ExtractHGlobalHandler<T>(IntPtr hGlobal);

		private static T ExtractHGlobal<T>(System.Runtime.InteropServices.ComTypes.IDataObject data,
			uint cfFormat, ExtractHGlobalHandler<T> handler)
			where T: class
		{
			FORMATETC format = new FORMATETC();
			format.cfFormat = (short)cfFormat;
			format.dwAspect = DVASPECT.DVASPECT_CONTENT;
			format.lindex = -1;
			format.tymed = TYMED.TYMED_HGLOBAL;

			if (data.QueryGetData(ref format) != S_OK)
			{
				return null;
			}
			STGMEDIUM medium = new STGMEDIUM();
			data.GetData(ref format, out medium);

			IntPtr hGlobal = GlobalLock(medium.unionmember);
			if (hGlobal == IntPtr.Zero)
			{
				return null;
			}

			T ret;
			try
			{
				ret = handler.Invoke(hGlobal);
			}
			finally
			{
				GlobalUnlock(hGlobal);
				ReleaseStgMedium(ref medium);
			}
			return ret;
		}

		public static string[] GetFileNames(System.Runtime.InteropServices.ComTypes.IDataObject data)
		{
			return ExtractHGlobal(data, CF_HDROP, delegate(IntPtr hDrop)
			{
				List<string> list = new List<string>();
				StringBuilder buf = new StringBuilder(260);
				uint count = DragQueryFile(hDrop, UInt32.MaxValue, IntPtr.Zero, 0);
				for (uint i = 0; i < count; i++)
				{
					uint len = DragQueryFile(hDrop, i, IntPtr.Zero, 0);
					buf.Capacity = Math.Max(buf.Capacity, (int)len);

					if (DragQueryFile(hDrop, i, buf, (uint)buf.Capacity) != len)
					{
						Dbg.Abort("DragQueryFile length error");
					}
					list.Add(buf.ToString());
				}
				return list.ToArray();
			});
		}

		public static Address[] GetAddresses(System.Runtime.InteropServices.ComTypes.IDataObject data)
		{
			return ExtractHGlobal(data, RegisterClipboardFormat(CFSTR_SHELLIDLIST), delegate(IntPtr hGlobal)
			{
				int intSize = Marshal.SizeOf(typeof(int));
				int cidl = Marshal.ReadInt32(hGlobal, 0);

				List<IntPtr> pointers = new List<IntPtr>();
				for (int i = 0; i < cidl + 1; i++)
				{	// 親パスも含める
					int offset = Marshal.ReadInt32(hGlobal, (i + 1) * intSize);	// cidl をスキップ
					//byte[] bytes = new byte[offset - prev];
					//Marshal.Copy(new IntPtr( (int)hGlobal + offset ), bytes, 0, bytes.Length);
					//list.Add(bytes);
					//prev = offset;

					//Address addr = new Address(new IntPtr((int)hGlobal + offset));
					//addr.ToString();
					pointers.Add(new IntPtr((int)hGlobal + offset));
				}
				IntPtr parent = pointers[0];
				pointers.RemoveAt(0);
				pointers = pointers.ConvertAll(ptr => ILCombine(parent, ptr));

				List<Address> addrs = pointers.ConvertAll(ptr => new Address(ptr));
				pointers.ForEach(ptr => ILFree(ptr));	// 解放

				return addrs.ToArray();
			});
		}

		public static string AddressToPath(Address addr)
		{
			if (addr.Path != null)
			{
				//Dbg.Echo(addr.Path);
				return addr.Path;
			}
			if (addr.ITEMIDLIST == null)
			{
				//Dbg.Abort("ITEMIDLIST is null");
				return null;
			}

			int len = addr.ITEMIDLIST.Length;
			IntPtr pidl = CoTaskMemAlloc((uint)len);
			Marshal.Copy(addr.ITEMIDLIST, 0, pidl, len);

			StringBuilder buf = new StringBuilder(260);
			if (!SHGetPathFromIDListW(pidl, buf))	//NOTE: 仮想フォルダはたぶん無理
			{
				return null;
			}
			CoTaskMemFree(pidl);
			return buf.ToString();
		}

		public static string Quote(string str)
		{
			return '"' + str + '"';
		}
	}

	public static class PluginInformation
	{
		// サーバはウィンドウごとに異なる
		//public static IPluginServer Server { get; set; }
		public static List<IPluginClient> Instances { get; private set; }

		static PluginInformation()
		{
			Instances = new List<IPluginClient>();
		}

		public static RegistryKey OpenRegistryKey(bool writable)
		{
			return OpenRegistryKey(typeof(PluginInformation), writable);
		}

		private static RegistryKey OpenRegistryKey(Type t, bool writable)
		{
			string ns = t.Namespace.Replace('.', '\\');
			string key = String.Format(@"Software\Quizo\QTTabBar\Plugins\Settings\{0}", ns);
			if (writable)
			{
				return Registry.CurrentUser.CreateSubKey(key);
			}
			else
			{
				return Registry.CurrentUser.OpenSubKey(key);
			}
		}

		public static RegistryKey OpenClassRegistryKey(Type t, bool writable)
		{
			RegistryKey parent = OpenRegistryKey(writable);
			if (writable)
			{
				return parent.CreateSubKey(t.Name);
			}
			else
			{
				if (parent == null)
				{
					return null;
				}
				return parent.OpenSubKey(t.Name);
			}
		}
	}

	public class PluginClient : IPluginClient
	{
		protected IPluginServer m_server = null;
		protected IShellBrowser m_browser = null;

		public virtual bool HasOption
		{
			get
			{
				return false;
			}
		}

		public void Open(IPluginServer pluginServer, IShellBrowser shellBrowser)
		{
			m_server = pluginServer;
			m_browser = shellBrowser;

			OnOpen();
		}

		public virtual void Close(EndCode endCode)
		{
		}

		public virtual bool QueryShortcutKeys(out string[] actions)
		{
			actions = new string[] { };
			return false;
		}

		public virtual void OnOption()
		{
		}

		public virtual void OnShortcutKeyPressed(int index)
		{
		}

		public virtual void OnMenuItemClick(MenuType menuType, string menuText, ITab tab)
		{
		}

		protected virtual void OnOpen()
		{
		}
	}

	public class BarButton : PluginClient, IBarButton
	{
		public virtual bool ShowTextLabel
		{
			get
			{
				return false;
			}
		}

		public virtual string Text
		{
			get
			{
				return String.Empty;
			}
		}

		public virtual void InitializeItem()
		{
			// ボタンバーにこのボタンが追加されるたびに呼ばれます。
		}

		public virtual Image GetImage(bool fLarge)
		{
			return null;
		}

		public virtual void OnButtonClick()
		{
		}

	}

	public class BarDropButton : BarButton, IBarDropButton
	{
		public virtual bool IsSplitButton
		{
			get
			{
				return false;
			}
		}

		public virtual void OnDropDownItemClick(ToolStripItem item, MouseButtons mouseButton)
		{
		}

		public virtual void OnDropDownOpening(ToolStripDropDownMenu menu)
		{
		}
	}

	//public class BarCustomItem : BarButton, IBarCustomItem
	//{
	//    public ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle)
	//    {
	//        return null;
	//    }
	//}


}
